package me.acomma.groot.interfaces.config;

import me.acomma.groot.application.auth.AuthApplicationService;
import me.acomma.groot.interfaces.shiro.CustomDefaultWebSessionManager;
import me.acomma.groot.interfaces.shiro.CustomFormAuthenticationFilter;
import me.acomma.groot.interfaces.shiro.CustomRealm;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {
    @Bean
    public Realm realm(AuthApplicationService authApplicationService) {
        return new CustomRealm(authApplicationService);
    }

    @Bean
    public CacheManager cacheManager() {
        return new MemoryConstrainedCacheManager();
    }

    @Bean
    public SessionManager sessionManager() {
        return new CustomDefaultWebSessionManager();
    }

    @Bean
    public SecurityManager securityManager(AuthApplicationService authApplicationService) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm(authApplicationService));
        securityManager.setCacheManager(cacheManager());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(AuthApplicationService authApplicationService) {
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("authc", new CustomFormAuthenticationFilter());

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/api/auth/login", "anon");
        filterChainDefinitionMap.put("/api/**", "authc");
        filterChainDefinitionMap.put("/**", "authc");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager(authApplicationService));
        shiroFilterFactoryBean.setFilters(filters);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    // 解决 bean，比如 roleApplicationService，被代理两次的问题，可以参考：
    // 1. [spring AOP二次代理的原因及错误](https://my.oschina.net/u/923696/blog/895187)
    // 2. [Springboot与shiro整合遇到的坑](https://zhuanlan.zhihu.com/p/29161098)
    // 3. [spring的二次代理原因及如何排查](https://www.iteye.com/blog/jinnianshilongnian-1894465)
    // 4. [Shiro权限注解与Aop冲突的前世今生](http://www.ice-maple.com/2020/05/23/Shiro%E6%9D%83%E9%99%90%E6%B3%A8%E8%A7%A3%E4%B8%8EAop%E5%86%B2%E7%AA%81%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/)
    // 5. [切面编程(三):AspectJ与Shiro不兼容和Spring二次代理错误分析](https://alanli7991.github.io/2016/10/21/%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B%E4%B8%89AspectJ%E4%B8%8EShiro%E4%B8%8D%E5%85%BC%E5%AE%B9%E5%92%8CSpring%E4%BA%8C%E6%AC%A1%E4%BB%A3%E7%90%86%E9%94%99%E8%AF%AF%E5%88%86%E6%9E%90/)
    //@Bean
    //@DependsOn({"lifecycleBeanPostProcessor"})
    //public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
    //    DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    //    advisorAutoProxyCreator.setProxyTargetClass(true);
    //    return advisorAutoProxyCreator;
    //}

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(AuthApplicationService authApplicationService) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager(authApplicationService));
        return authorizationAttributeSourceAdvisor;
    }
}
